﻿using System;
using System.Collections.Generic;
using System.Xml;

namespace IMSAT.OFTD.DOM
{
    /// <summary>
    /// Синглтон.
    /// Класс для храния и выборки ридеров и врайтеров.
    /// </summary>
    public class RWHolder
    {
        #region Delegates

        /// <summary>
        /// Делегат описывающий как нужно подготавливать 
        /// источник перед проверкой на возможность чтения из него
        /// </summary>
        /// <param name="src">Источник</param>
        public delegate void PrepareSource(object src);

        #endregion

        /// <summary>
        /// Текущий объект класса.
        /// </summary>
        private static RWHolder _instace;

        /// <summary>
        /// Список ридеров
        /// </summary>
        private readonly List<IReader> _readers = new List<IReader>();

        /// <summary>
        /// Словарь соотвествий типа ресурса и делегата для
        /// подготовки его к проверке
        /// </summary>
        private readonly Dictionary<Type, PrepareSource> _sourcePrepare = new Dictionary<Type, PrepareSource>();

        /// <summary>
        /// Список врайтеров
        /// </summary>
        private readonly List<IWriter> _writers = new List<IWriter>();

        /// <summary>
        /// Конструктор
        /// </summary>
        private RWHolder()
        {
            RegisterReader(new Node.NodeXmlReader());
            _sourcePrepare.Add(typeof (XmlReader), delegate(object source)
                                                       {
                                                           if (source is XmlReader)
                                                           {
                                                               while ((source as XmlReader).Read())
                                                               {
                                                                   if ((source as XmlReader).NodeType ==
                                                                       XmlNodeType.Element)
                                                                       break;
                                                               }
                                                           }
                                                       });
        }

        /// <summary>
        /// Возвращает текущий объект класса
        /// </summary>
        /// <returns>текущий объект класса</returns>
        public static RWHolder GetInstace()
        {
            if (_instace == null)
                _instace = new RWHolder();
            return _instace;
        }

        /// <summary>
        /// Зарегестрировать ридер. Если ридер уже присутсвует в списке от не добавляется
        /// </summary>
        /// <param name="rd">ридер</param>
        public void RegisterReader(IReader rd)
        {
            if (!_readers.Contains(rd))
            {
                _readers.Add(rd);
            }
        }

        /// <summary>
        /// Зарегестрировать врайтер. Если врайтер есть в списке то он не добавляется.
        /// </summary>
        /// <param name="wr"></param>
        public void RegisterWriter(IWriter wr)
        {
            if (!_writers.Contains(wr))
            {
                _writers.Add(wr);
            }
        }

        /// <summary>
        /// Получить ридер по источнику и типу
        /// в который он будет читать.
        /// Если ридер не найден возвращается Null.
        /// </summary>
        /// <remarks>
        /// Если существует несколько ридеров то
        /// они будут сортироватся в соотвествии с деревом наследования.
        /// Выбран будет самый последний потомок.
        /// </remarks>
        /// <param name="destType">тип в который должен читать ридер</param>
        /// <param name="source">Источник</param>
        /// <returns>ридер</returns>
        public IReader GetReader(Type destType, object source, Node prevNode)
        {
            var res = new List<IReader>();
            foreach (var kvp in _sourcePrepare)
            {
                if (kvp.Key.IsInstanceOfType(source))
                    kvp.Value(source);
            }
            foreach (IReader r in _readers)
            {
                if (r.CanRead(source, destType, prevNode))
                {
                    var rem_list = new List<IReader>();
                    foreach (IReader r1 in res)
                    {
                        if (r.GetType().IsSubclassOf(r1.GetType()))
                            rem_list.Add(r1);
                    }
                    foreach (IReader r1 in rem_list)
                        res.Remove(r1);
                    res.Add(r);
                }
            }
            if (res.Count >= 1)
            {
                return res[0];
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Получить врайтер по типу того что будут писать и 
        /// объекту куда будут писать
        /// </summary>
        /// <param name="sourceType">тип того что будут писать</param>
        /// <param name="dest">куда будут писать</param>
        /// <returns>врайтер</returns>
        public IWriter GetWriter(Type sourceType, object dest)
        {
            var res = new List<IWriter>();
            foreach (IWriter wr in _writers)
            {
                if (wr.GetType().IsGenericType)
                {
                    Type wrtype = wr.GetType();
                    Type[] ta = wrtype.GetGenericArguments();
                    foreach (Type t in ta)
                    {
                        if (t == sourceType)
                            res.Add(wr);
                    }
                }
            }
            var fw = new List<IWriter>();
            foreach (IWriter wr in res)
            {
                if (wr.GetType().IsGenericType)
                {
                    Type wrtype = wr.GetType();
                    Type[] ta = wrtype.GetGenericArguments();
                    foreach (Type t in ta)
                    {
                        if (t == dest.GetType())
                            fw.Add(wr);
                    }
                }
            }
            if (fw.Count > 1)
            {
                return fw[0];
            }
            return null;
        }
    }
}